Trabajo práctico nº1: regresión lineal

 

Datos a utilizar:

Los datos con los que se trabajará en este TP provienen de la 3° Encuesta Mundial de Salud Escolar (EMSE) provistos por el Ministerio de Salud de la República Argentina. Esta encuesta trata sobre temas de salud y hábitos de las personas en la escuela secundaria que pueden impactar en su salud.

Los datasets que se comparten corresponden a un recorte (muestra) del dataset original, luego del tratamiento de valores atípicos e ingeniería de atributos.

Las variables incluidas son:

Variable Descripción
record ID de la observación
edad Edad en años
genero género de la persona
nivel_educativo nivel educativo en que se encuentra la persona
altura altura en centímetros
peso peso en kilogramos
frecuencia_hambre_mensual variable categórica que indica la frecuencia con la que la persona considera que pasó hambre en el último mes porque no había suficiente comida en su hogar
dias_consumo_comida_rapida cuántos días comió en un restaurante de comida rápida en la última semana
edad_consumo_alcohol edad en qué la persona comenzó a consumir alcohol
consumo_diario_alcohol cantidad de tragos que la persona habitualmente toma por día
dias_actividad_fisica_semanal cantidad de días que la persona realizó una actividad física por un total de al menos 60 minutos en la última semana
consumo_semanal_frutas cantidad de veces que la persona consumió frutas en la última semana
consumo_semanal_verdura cantidad de veces que la persona consumió gaseosas (al menos un vaso) en la última semana
consumo_semanal_gaseosas cantidad de veces que la persona consumió snacks/comida salada en la última semana
consumo_semanal_snacks cantidad de veces que la persona consumió snacks/comida salada en la última semana
consumo_semanal_comida_grasa cantidad de veces que la persona consumió comidas altas en grasas en la última semana

Consignas:

El objetivo general del trabajo es poder crear una serie de modelos lineales para explicar y predecir el peso de los estudiantes según la información que proporciona la EMSE.


 Se cargan las librerías con las que se va a realizar el trabajo:

library(tidyverse)
library(tidymodels)
library(ggplot2)
library(knitr)
library(GGally)
library(robust)

options(scipen=999)

 

Importo el dataset:

encuesta_salud <- read.csv("encuesta_salud_train.csv")


1) Análisis exploratorio


Primero, observo algunos registros del dataset:

encuesta_salud %>% sample_n(5)


Observo las dimensiones del dataset:

glimpse(encuesta_salud)
Rows: 7,024
Columns: 16
$ record                        <int> 502, 26488, 31473, 14154, 36578, 53730, 11892, 35549, 14928, 11754, 40850, 11038, 32832, 31300, 32497, 6961, 39376, 30541, 18…
$ edad                          <int> 17, 15, 15, 16, 17, 15, 13, 17, 17, 16, 16, 14, 15, 17, 15, 14, 15, 17, 17, 16, 14, 12, 15, 17, 15, 16, 16, 16, 17, 15, 17, 1…
$ genero                        <chr> "Femenino", "Masculino", "Masculino", "Masculino", "Masculino", "Masculino", "Femenino", "Femenino", "Masculino", "Femenino",…
$ nivel_educativo               <chr> "2do año/11vo grado nivel Polimodal o 4to año nivel Secundario", "1er año/10mo grado nivel Polimodal o 3er año nivel Secundar…
$ altura                        <int> 165, 178, 172, 170, 170, 178, 156, 163, 164, 167, 185, 146, 180, 175, 183, 165, 165, 157, 165, 170, 174, 163, 154, 164, 160, …
$ peso                          <int> 62, 62, 62, 65, 75, 88, 46, 60, 57, 51, 100, 33, 62, 70, 80, 60, 47, 50, 50, 70, 75, 55, 64, 59, 86, 51, 71, 49, 70, 65, 65, …
$ frecuencia_hambre_mensual     <chr> "Rara vez", "Rara vez", "Nunca", "Nunca", "Rara vez", "Nunca", "Nunca", "Nunca", "Nunca", "Nunca", "Nunca", "Rara vez", "Nunc…
$ dias_consumo_comida_rapida    <int> 0, 0, 3, 1, 1, 2, 0, 0, 0, 3, 4, 2, 1, 1, 3, 0, 0, 0, 0, 1, 0, 6, 0, 1, 0, 2, 0, 2, 0, 0, 5, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0,…
$ edad_consumo_alcohol          <chr> "14 o 15 años", "7 años o menos", "Nunca tomé alcohol más que unos pocos sorbos", "14 o 15 años", "16 o 17 años", "8 o 9 años…
$ consumo_diario_alcohol        <dbl> 5.0, 4.0, 0.0, 0.0, 0.0, 5.0, 1.0, 0.5, 5.0, 0.0, 5.0, 0.0, 0.0, 2.0, 1.0, 0.0, 5.0, 0.0, 1.0, 1.0, 0.5, 0.0, 3.0, 3.0, 1.0, …
$ dias_actividad_fisica_semanal <int> 7, 7, 7, 7, 0, 7, 0, 2, 7, 3, 2, 2, 7, 1, 4, 0, 1, 6, 5, 7, 3, 0, 7, 5, 2, 2, 4, 2, 7, 4, 7, 0, 6, 4, 0, 3, 2, 0, 2, 2, 3, 0,…
$ consumo_semanal_frutas        <chr> "No comí frutas durante los últimos 7 días", "No comí frutas durante los últimos 7 días", "No comí frutas durante los últimos…
$ consumo_semanal_verdura       <chr> "4 a 6 veces durante los últimos 7 días", "4 a 6 veces durante los últimos 7 días", "1 vez al día", "4 o más veces al día", "…
$ consumo_semanal_gaseosas      <chr> "1 a 3 veces durante los últimos 7 días", "1 a 3 veces durante los últimos 7 días", "4 a 6 veces durante los últimos 7 días",…
$ consumo_semanal_snacks        <chr> "1 a 3 veces durante los últimos 7 días", "No comí comida salada o snacks en los últimos 7 días", "4 a 6 veces durante los úl…
$ consumo_semanal_comida_grasa  <chr> "No comí comida alta en grasa en los últimos 7 días", "4 a 6 veces durante los últimos 7 días", "No comí comida alta en grasa…

Aquí se puede ver que el dataset cuenta con 7024 observaciones para las cuales se tienen 16 variables (7 de ellas numéricas y 9 categóricas). La variable peso, que es una de las variables numéricas, es la variable a explicar con el resto.

A continuación, observamos si las variables numéricas - y fundamentalmente el peso - están correlacionadas o no. Para ello, se procede a aperturar por el género de cada una de las personas:

encuesta_salud %>%
  select(where(is.numeric), genero, -record) %>%
  ggpairs(mapping = aes(color = genero), title = "Matriz de correlaciones",
          upper = list(continuous = wrap("cor", size = 5, hjust=0.5)), legend = 25) +
  theme_bw() +
  theme(axis.text.x = element_text(angle=45, vjust=0.5), legend.position = "bottom")

En los gráficos, se observa que existe cierta correlación entre entre el peso y la altura (0,57 para las personas de género masculino, 0,44 para las de género femenino. También hay una correlación moderada entre la edad y el peso.

encuesta_salud %>% 
  group_by(genero) %>% 
  summarise(cor = cor(altura, peso))


Luego, observamos qué categorías existen para la “frecuencia de hambre mensual”:

encuesta_salud %>%
  select(frecuencia_hambre_mensual) %>%
  table() %>%
  prop.table() %>%
  sort(decreasing = TRUE)
.
        Nunca      Rara vez Algunas veces  Casi siempre  Dato perdido       Siempre 
  0.689635535   0.206007973   0.083855353   0.011104784   0.005552392   0.003843964 


Para cada una de ellas, observamos cómo se distribuyen el consumo semanal de verdura y el consumo semanal de comida grasa:

tabla = encuesta_salud %>%
  filter(frecuencia_hambre_mensual != 'Dato perdido') %>%
  filter(consumo_semanal_verdura != 'Dato perdido') %>%
  select(consumo_semanal_verdura, frecuencia_hambre_mensual) %>%
  table()
tabla = as.data.frame(tabla)
colnames(tabla) <- c("consumo_semanal_verdura", "frecuencia_hambre_mensual", "q")

order = c("Nunca", "Rara vez", "Algunas veces", "Casi siempre", "Siempre")
type = c("4 o más veces al día", "3 veces al día", "2 veces al día", "1 vez al día", "4 a 6 veces durante los últimos 7 días", "1 a 3 veces durante los últimos 7 días", "No comí verduras ni hortalizas durante los últimos 7 días")


ggplot(tabla) +
 aes(x = frecuencia_hambre_mensual, 
     fill = factor(str_wrap(consumo_semanal_verdura, 20), levels = str_wrap(type, 20)),
     weight = q) +
 geom_bar(position = "fill") +
 scale_fill_hue(direction = 1) +
 coord_flip() +
 theme_minimal() +
 theme(legend.position = "bottom") + 
 theme(legend.title=element_blank()) + 
 labs(x = "Frecuencia de hambre",  y = "Consumo de verdura (proporción)", title = "Consumo de verduras y cantidad de veces con hambre en el mes") + 
 scale_x_discrete(limits = order)

En estos gráficos, se puede observar que aquellos que suelen pasar hambre (siempre o casi siempre) registran un menor consumo de verduras y hortalizas. En los primeros también hay un alto porcentaje de peresonas que comen verduras y/o hortalizas 4 o más veces por día lo que de alguna manera también habla de lo poco balanceadas que son sus dietas.


2) Modelo inicial

La primera alternativa para modelar el peso, es la siguiente:

\[ E(peso) = \beta_{0} + \beta_{1} altura+ \beta_{2} edad+ \beta_{3} genero + \beta_{4} diasActividadF isicaSemanal + \beta_{5} consumoDiarioAlcohol \]

Ajusto el modelo lineal:

modelo_simple = lm(peso ~ altura + edad + genero + dias_actividad_fisica_semanal + consumo_diario_alcohol, data=encuesta_salud)

tidy_modelo_simple <- tidy(modelo_simple, conf.int = TRUE)
tidy_modelo_simple


El valor de \(\beta_{0}\) nos indica que el peso esperado para una mujer de 0 cm de altura, 0 años, que no realiza actividad física y que no consume alcohol es de -68.92 kg.

El coeficiente que multiplica a la altura \(\beta_{1}\) es 0.65. Significa que ante cada cm adicional de altura el peso promedio aumenta en 0,65 kg manteniendo todas las otras variables constantes

El coeficiente que multiplica a generoMasculino es 1.26, de lo que se deduce que el peso esperado para una persona recién nacida de género masculino de 0 cm de altura, que no realiza actividad física ni consume alcohol es de -67,65 (-68.92 + 1.2626).

El valor de \(\beta_{4}\) nos muestra que ante cada día de actividad física adicional el peso promedio disminuye en 0,08kg (manteniendo todas las otras variables constantes).

El valor de \(\beta_{5}\) nos indica que ante cada trago diario adicional de alcohol el peso esperado de una persona aumenta en 0,007kg (manteniendo todas las otras variables constantes).

Sin embargo, no todas las variables son significativas. Tras realizar el test de significatividad individual para los días de actividad física semanal y para el consumo diario de alcohol se observan p-valores mayores a 0.05 por lo que no se rechaza la hipótesis nula (\(\beta_{k}=0\)). También se puede ver que los intervalos de confianza para ambos coeficientes contienen el 0, lo que nos da la pauta de que no son variables útiles para explicar el peso de una persona.

Por otro lado, el resto de las variables (altura, edad y género) sí resultan estadísticamente significativas para explicar el peso de una persona.

glance(modelo_simple)

El \(R^{2}\) del modelo, el porcentaje de variabilidad del fenómeno que el modelo logra explicar, es de 0,35. Considerando el p-valor, se rechaza la hipótesis nula del test de significatividad global y podemos concluir que al menos una de las variables regresoras sirve para explicar el peso de una persona.


3) Modelo categóricas

Luego, se probará con un modelo que incopora el consumo semanal de snacks y una interacción entre el género y la edad, en lugar de actividad física y consumo de alcohol:

\[ E(peso) = \beta_{0} + \beta_{1} altura+ \beta_{2} edad+ \beta_{3} genero + \beta_{4} consumoSemanalSnacks + \beta_{5} genero.edad \]

Se harán algunas modificaciones para que el nivel basal del consumo semanal de snacks sea “no comí comida salada o snacks en los últimos 7 días:

encuesta_salud$consumo_semanal_snacks <- relevel(as.factor(encuesta_salud$consumo_semanal_snacks), ref = "No comí comida salada o snacks en los últimos 7 días")

modelo_categoricas = lm(peso ~ altura + edad + genero + consumo_semanal_snacks + genero * edad, data=encuesta_salud)

tidy_modelo_categoricas <- tidy(modelo_categoricas, conf.int = TRUE)
tidy_modelo_categoricas


En este caso \(\beta_{0}\) nos indica que el peso promedio de una mujer de 0 años, 0 cm, que no consume comida salada ni snacks es de -64.2 kg.

Las distintas categorías de consumoSemanalSnacks (por ejemplo, consumo_semanal_snacks1 a 3 veces durante los últimos 7 días) reflejan la diferencia de peso entre una persona que no consume snacks vs una persona que consume snacks con la frecuencia que refleja la categoría. No todas las categorías de consumo semanal de snacks resultan significativas: aquellas que surgen de consumir snacks todos los días son las que no lo son.

Por otro lado, el coeficiente edad.generoMasculino en este caso refleja que, para los hombres, un año adicional de edad aumenta el peso promedio en 1,22 + 0,39 kg manteniendo constantes el resto de las variables. La variable tiene un efecto significativo para explicar el peso.

Vamos a ver qué porcentaje de la variabilidad explica el modelo:

glance(modelo_categoricas)


Por último, considerando que algunas categorías consumoSemanalSnacks no resultan significativas, se va a realizar un test F para evaluar la significatividad conjunta de la variable para explicar al peso.

tidy(anova(modelo_categoricas))

Considerando el p-valor, se concluye que la variable consumo_semanal_snacks es significativa para explicar el peso de una persona.

A continuación, se va a proponer un nueva definición de las categorías de consumoSemanalSnacks: se van a juntar “consume snacks 1 vez al día”, “consume snacks 2 veces al día”, “consume snacks 3 veces al día” y “consume snacks 4 o más veces al día” en una única categoría “consume snacks todos los días”, y se van a volver a estimar los coeficientes.

encuesta_salud$consumo_semanal_snacks_new <- ifelse(encuesta_salud$consumo_semanal_snacks %in% c("1 vez al día", "2 veces al día", "3 veces al día", "4 o más veces al día"), 
                                                    "Consume snacks todos los días", 
                                                    as.character(encuesta_salud$consumo_semanal_snacks))
encuesta_salud$consumo_semanal_snacks_new <- relevel(as.factor(encuesta_salud$consumo_semanal_snacks_new), ref = "No comí comida salada o snacks en los últimos 7 días")

modelo_categoricas_grouped = lm(peso ~ altura + edad + genero + consumo_semanal_snacks_new + genero * edad, data=encuesta_salud)

tidy_modelo_categoricas_grouped <- tidy(modelo_categoricas_grouped, conf.int = TRUE)
tidy_modelo_categoricas_grouped
NA


Se observa que todas las variables incluídas en el nuevo modelo son significativas (incluídas todas las categorías de consumo semanal de snacks).

A continuación, se va a consultar la variabilidad explicada por el modelo:

glance(modelo_categoricas_grouped)

Con la nueva agrupación no hay una mejora en el \(R^2\) del modelo.


4) Modelos propios y evaluación

A continuación, se sugieren dos posibles modelos adicionales para intentar explicar el peso de una persona:

A: \[ E(peso) = \beta_{0} + \beta_{1} altura+ \beta_{2} edad+ \beta_{3} genero + \beta_{4} consumoSemanalSnacksNew + \\ \beta_{5} consumoSemanalGaseosa + \beta_{6} diasConsumoComidaRapida + \beta_{7} genero.edad + \beta_{8} genero.altura \]

B:

\[ E(peso) = \beta_{0} + \beta_{1} altura+ \beta_{2} edad+ \beta_{3} genero + \beta_{4} consumoSemanalSnacksNew + \beta_{5} frecuenciaHambreMensual + \\ \beta_{6} genero.edad + \beta_{7} genero.altura + \beta_{8} edadConsumoAlcohol . consumoDiarioAlcohol \]

El primer modelo propuesto incorpora una interacción entre género y altura, y el consumo semanal de gaseosa y de comida rápida.

El segundo modelo propuesto, además de la interacción entre género y altura, incorpora la frecuencia de hambre mensual. y también una interacción que consiste en multiplicar la edad en la que comenzaron a consumir alcohol y el consumo diario.

modelo_a = lm(peso ~ altura + edad + genero + consumo_semanal_snacks_new + consumo_semanal_gaseosas + dias_consumo_comida_rapida + genero * edad + genero * altura, data=encuesta_salud)

tidy_modelo_a <- tidy(modelo_a, conf.int = TRUE)
tidy_modelo_a

breve comentario de la significatividad de las variables.

modelo_b = lm(peso ~ altura + edad + genero + consumo_semanal_snacks_new + frecuencia_hambre_mensual + genero * edad + genero * altura + edad_consumo_alcohol * consumo_diario_alcohol, data=encuesta_salud)

tidy_modelo_b <- tidy(modelo_b, conf.int = TRUE)
tidy_modelo_b

breve comentario de la significatividad de las variables.

A continuación, se van a comparar los distintos modelos desarrollados. En primer lugar, se carga el dataset de test en el que vamos a medir la performance de los modelos:

encuesta_salud_test <- read.csv("encuesta_salud_test.csv")

Se crea la variable consumo semanal snacks con la nueva agrupación también para el dataset de test:

encuesta_salud_test$consumo_semanal_snacks_new <- ifelse(encuesta_salud_test$consumo_semanal_snacks %in% c("1 vez al día", "2 veces al día", "3 veces al día", "4 o más veces al día"), 
                                                    "Consume snacks todos los días", 
                                                    as.character(encuesta_salud_test$consumo_semanal_snacks))

Se arma una lista con todos los modelos construídos

models <- list(modelo_simple = modelo_simple, 
               modelo_categorias = modelo_categoricas_grouped, 
               modelo_a = modelo_a, 
               modelo_b = modelo_b)

En el dataset de train, se observa el r2 de todos los modelos

evaluacion_train = map_df(models, glance, .id = "model") %>%
  # ordenamos por R2 ajustado
  arrange(desc(adj.r.squared))

evaluacion_train

breve comentario de los r2 y los r2 ajust.

A continuación, se van a calcular RMSE y MAE tanto para train como para test:

lista_predicciones_training = map(.x = models, .f = augment)
lista_predicciones_testing = map(.x = models, .f = augment, newdata = encuesta_salud_test)

cbind(map_dfr(.x = lista_predicciones_training, .f = rmse, truth = peso, estimate = .fitted, .id="modelo") %>% rename(rmse_train = .estimate) %>% select(modelo, rmse_train) %>% arrange(modelo),
map_dfr(.x = lista_predicciones_training, .f = mae, truth = peso, estimate = .fitted, .id="modelo") %>% rename(mae_train = .estimate) %>% arrange(modelo) %>% select(mae_train),
map_dfr(.x = lista_predicciones_testing, .f = rmse, truth = peso, estimate = .fitted, .id="modelo") %>% rename(rmse_test = .estimate) %>% arrange(modelo) %>% select(rmse_test),
map_dfr(.x = lista_predicciones_testing, .f = mae, truth = peso, estimate = .fitted, .id="modelo") %>% rename(mae_test = .estimate) %>% arrange(modelo) %>% select(mae_test)) %>% arrange(rmse_test)
NA

Para definir cual es el mejor modelo, se decide comparar el RMSE en test de los distintos modelo con el objetivo de entender qué tan bien generaliza el modelo en datos nuevos:

Se observa que el mejor modelo es X que tiene el menor RMSE en test. y además, se observa que coincide con que es también el de menor X X X X


5) Diagnóstico del modelo

En la siguiente sección, se observará el cumplimiento de los supuestos del modelo lineal para el modelo inicial.

Los supuestos a analizar son los siguientes:

plot(modelo_simple)

Al utilizar plot() sobre el modelo ajustado, se pueden observar varios gráficos que nos van a permitir analizar los supuestos del modelo lineal:

Residuos vs valores predichos: No parece existir una estructura clara entre los residuos y los valores predichos. Sucede algo similar en el gráfico scale-location.

Normal QQ plot: El extremo superior derecho no se ajusta a la distribución teórica, en este caso la ∼N(0,1), por lo que se deduce que los residuos estandarizados no siguen esa distribución.

Residual vs leverage: Existen algunos puntos con un leverage bastante alto. Vamos a ver cuál es la observación para entender si se trata de un posible outlier:

lista_predicciones_training$modelo_simple %>%
  filter(.hat == max(.hat))
NA

6) Modelo robusto

Por último, se va a leer un nuevo dataset con algunos valores atípicos y vamos a volver a observar la relación entre peso y altura:

encuesta_salud_outliers <- read.csv("encuesta_salud_modelo6.csv")

Observamos el modelo ajustado:

modelo_robusto <- lmRob(peso ~ altura + edad + genero + dias_actividad_fisica_semanal + consumo_diario_alcohol,data = encuesta_salud_outliers)

tidy_modelo_robusto <- tidy(modelo_robusto)
tidy_modelo_robusto

coeficientes estimados:

los coeficientes que multiplican la altura y la edad no cambian sustancialmente, y siguen siendo significativos para explicar el peso de una persona. algo similar sucede con el coeficiente que acompaña al género. Días de actividad física semanal y consumo diario de alcohol siguen siendo no significativas para explicar el peso.

Ahora observamos el porcentaje de la varianza explicada por el modelo:

glance(modelo_robusto)

se observa una baja considerable del \(R^2\) de este nuevo modelo. Pasa de cerca del 0,35 a un 0,28.

A continuación, vamos a observar que tan bien performa el modelo en datos nuevos. Se va a utilizar nuevamente el RMSE y el MAE:

si comparamos el rmse y el mae vs los modelos anteriormente analizados, se puede ver que no se deterioró mucho la predicción del modelo. sí, podría

LS0tCnRpdGxlOiAiRW5mb3F1ZSBlc3RhZMOtc3RpY28gZGVsIGFwcmVuZGl6YWplIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIFsqKlRyYWJham8gcHLDoWN0aWNvIG7CujE6IHJlZ3Jlc2nDs24gbGluZWFsKipdey51bmRlcmxpbmV9CgrCoAoKIyMjICoqRGF0b3MgYSB1dGlsaXphcjoqKgoKTG9zIGRhdG9zIGNvbiBsb3MgcXVlIHNlIHRyYWJhamFyw6EgZW4gZXN0ZSBUUCBwcm92aWVuZW4gZGUgbGEgM8KwIEVuY3Vlc3RhIE11bmRpYWwgZGUgU2FsdWQgRXNjb2xhciAoRU1TRSkgcHJvdmlzdG9zIHBvciBlbCBNaW5pc3RlcmlvIGRlIFNhbHVkIGRlIGxhIFJlcMO6YmxpY2EgQXJnZW50aW5hLiBFc3RhIGVuY3Vlc3RhIHRyYXRhIHNvYnJlIHRlbWFzIGRlIHNhbHVkIHkgaMOhYml0b3MgZGUgbGFzIHBlcnNvbmFzIGVuIGxhIGVzY3VlbGEgc2VjdW5kYXJpYSBxdWUgcHVlZGVuIGltcGFjdGFyIGVuIHN1IHNhbHVkLgoKTG9zIGRhdGFzZXRzIHF1ZSBzZSBjb21wYXJ0ZW4gY29ycmVzcG9uZGVuIGEgdW4gcmVjb3J0ZSAobXVlc3RyYSkgZGVsIGRhdGFzZXQgb3JpZ2luYWwsIGx1ZWdvIGRlbCB0cmF0YW1pZW50byBkZSB2YWxvcmVzIGF0w61waWNvcyBlIGluZ2VuaWVyw61hIGRlIGF0cmlidXRvcy4KCkxhcyAqKnZhcmlhYmxlcyoqIGluY2x1aWRhcyBzb246Cgp8ICAgICAgICAgICAgIFZhcmlhYmxlICAgICAgICAgICAgICB8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIERlc2NyaXBjacOzbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8Cnw6LS0tLS0tLS0tLS0tLS0tLS0tLS06fDotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLTp8CnwgICAgICAgICAgICAqKnJlY29yZCoqICAgICAgICAgICAgIHwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElEIGRlIGxhIG9ic2VydmFjacOzbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKfCAgICAgICAgICAgICAqKmVkYWQqKiAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEVkYWQgZW4gYcOxb3MgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8ICAgICAgICAgICAgKipnZW5lcm8qKiAgICAgICAgICAgICB8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnw6luZXJvIGRlIGxhIHBlcnNvbmEgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwgICAgICAgICoqbml2ZWxfZWR1Y2F0aXZvKiogICAgICAgIHwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbml2ZWwgZWR1Y2F0aXZvIGVuIHF1ZSBzZSBlbmN1ZW50cmEgbGEgcGVyc29uYSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8ICAgICAgICAgICAgKiphbHR1cmEqKiAgICAgICAgICAgICB8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbHR1cmEgZW4gY2VudMOtbWV0cm9zICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwgICAgICAgICAgICAgKipwZXNvKiogICAgICAgICAgICAgIHwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwZXNvIGVuIGtpbG9ncmFtb3MgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8ICAgKipmcmVjdWVuY2lhX2hhbWJyZV9tZW5zdWFsKiogICB8IHZhcmlhYmxlIGNhdGVnw7NyaWNhIHF1ZSBpbmRpY2EgbGEgZnJlY3VlbmNpYSBjb24gbGEgcXVlIGxhIHBlcnNvbmEgY29uc2lkZXJhIHF1ZSBwYXPDsyBoYW1icmUgZW4gZWwgw7psdGltbyBtZXMgcG9ycXVlIG5vIGhhYsOtYSBzdWZpY2llbnRlIGNvbWlkYSBlbiBzdSBob2dhciB8CnwgICoqZGlhc19jb25zdW1vX2NvbWlkYV9yYXBpZGEqKiAgIHwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjdcOhbnRvcyBkw61hcyBjb21pw7MgZW4gdW4gcmVzdGF1cmFudGUgZGUgY29taWRhIHLDoXBpZGEgZW4gbGEgw7psdGltYSBzZW1hbmEgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwgICAgICoqZWRhZF9jb25zdW1vX2FsY29ob2wqKiAgICAgIHwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlZGFkIGVuIHF1w6kgbGEgcGVyc29uYSBjb21lbnrDsyBhIGNvbnN1bWlyIGFsY29ob2wgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwgICAgKipjb25zdW1vX2RpYXJpb19hbGNvaG9sKiogICAgIHwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjYW50aWRhZCBkZSB0cmFnb3MgcXVlIGxhIHBlcnNvbmEgaGFiaXR1YWxtZW50ZSB0b21hIHBvciBkw61hICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKfCAqKmRpYXNfYWN0aXZpZGFkX2Zpc2ljYV9zZW1hbmFsKiogfCAgICAgICAgICAgICAgICAgICAgY2FudGlkYWQgZGUgZMOtYXMgcXVlIGxhIHBlcnNvbmEgcmVhbGl6w7MgdW5hIGFjdGl2aWRhZCBmw61zaWNhIHBvciB1biB0b3RhbCBkZSBhbCBtZW5vcyA2MCBtaW51dG9zIGVuIGxhIMO6bHRpbWEgc2VtYW5hICAgICAgICAgICAgICAgICAgICAgfAp8ICAgICoqY29uc3Vtb19zZW1hbmFsX2ZydXRhcyoqICAgICB8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjYW50aWRhZCBkZSB2ZWNlcyBxdWUgbGEgcGVyc29uYSBjb25zdW1pw7MgZnJ1dGFzIGVuIGxhIMO6bHRpbWEgc2VtYW5hICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8ICAgICoqY29uc3Vtb19zZW1hbmFsX3ZlcmR1cmEqKiAgICB8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNhbnRpZGFkIGRlIHZlY2VzIHF1ZSBsYSBwZXJzb25hIGNvbnN1bWnDsyBnYXNlb3NhcyAoYWwgbWVub3MgdW4gdmFzbykgZW4gbGEgw7psdGltYSBzZW1hbmEgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8ICAgKipjb25zdW1vX3NlbWFuYWxfZ2FzZW9zYXMqKiAgICB8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNhbnRpZGFkIGRlIHZlY2VzIHF1ZSBsYSBwZXJzb25hIGNvbnN1bWnDsyBzbmFja3MvY29taWRhIHNhbGFkYSBlbiBsYSDDumx0aW1hIHNlbWFuYSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8ICAgICoqY29uc3Vtb19zZW1hbmFsX3NuYWNrcyoqICAgICB8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNhbnRpZGFkIGRlIHZlY2VzIHF1ZSBsYSBwZXJzb25hIGNvbnN1bWnDsyBzbmFja3MvY29taWRhIHNhbGFkYSBlbiBsYSDDumx0aW1hIHNlbWFuYSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8ICoqY29uc3Vtb19zZW1hbmFsX2NvbWlkYV9ncmFzYSoqICB8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2FudGlkYWQgZGUgdmVjZXMgcXVlIGxhIHBlcnNvbmEgY29uc3VtacOzIGNvbWlkYXMgYWx0YXMgZW4gZ3Jhc2FzIGVuIGxhIMO6bHRpbWEgc2VtYW5hICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAoKIyMjICoqQ29uc2lnbmFzOioqCgpFbCBvYmpldGl2byBnZW5lcmFsIGRlbCB0cmFiYWpvIGVzIHBvZGVyIGNyZWFyIHVuYSBzZXJpZSBkZSBtb2RlbG9zIGxpbmVhbGVzIHBhcmEgZXhwbGljYXIgeSBwcmVkZWNpciBlbCBwZXNvIGRlIGxvcyBlc3R1ZGlhbnRlcyBzZWfDum4gbGEgaW5mb3JtYWNpw7NuIHF1ZSBwcm9wb3JjaW9uYSBsYSBFTVNFLgoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgrCoFNlIGNhcmdhbiBsYXMgbGlicmVyw61hcyBjb24gbGFzIHF1ZSBzZSB2YSBhIHJlYWxpemFyIGVsIHRyYWJham86CgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQpgYGAKCmBgYHtyfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeSh0aWR5bW9kZWxzKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoa25pdHIpCmxpYnJhcnkoR0dhbGx5KQpsaWJyYXJ5KHJvYnVzdCkKCm9wdGlvbnMoc2NpcGVuPTk5OSkKCmBgYAoKwqAKCkltcG9ydG8gZWwgZGF0YXNldDoKCmBgYHtyfQplbmN1ZXN0YV9zYWx1ZCA8LSByZWFkLmNzdigiZW5jdWVzdGFfc2FsdWRfdHJhaW4uY3N2IikKYGBgCgpcCgojIyMjIFsqKjEpIEFuw6FsaXNpcyBleHBsb3JhdG9yaW8qKl17LnVuZGVybGluZX0KClwKUHJpbWVybywgb2JzZXJ2byBhbGd1bm9zIHJlZ2lzdHJvcyBkZWwgZGF0YXNldDoKCmBgYHtyIGVjaG89VFJVRX0KZW5jdWVzdGFfc2FsdWQgJT4lIHNhbXBsZV9uKDUpCmBgYAoKXApPYnNlcnZvIGxhcyBkaW1lbnNpb25lcyBkZWwgZGF0YXNldDoKCmBgYHtyIGVjaG89VFJVRX0KZ2xpbXBzZShlbmN1ZXN0YV9zYWx1ZCkKYGBgCgpBcXXDrSBzZSBwdWVkZSB2ZXIgcXVlIGVsIGRhdGFzZXQgY3VlbnRhIGNvbiA3MDI0IG9ic2VydmFjaW9uZXMgcGFyYSBsYXMgY3VhbGVzIHNlIHRpZW5lbiAxNiB2YXJpYWJsZXMgKDcgZGUgZWxsYXMgbnVtw6lyaWNhcyB5IDkgY2F0ZWfDs3JpY2FzKS4gTGEgdmFyaWFibGUgcGVzbywgcXVlIGVzIHVuYSBkZSBsYXMgdmFyaWFibGVzIG51bcOpcmljYXMsIGVzIGxhIHZhcmlhYmxlIGEgZXhwbGljYXIgY29uIGVsIHJlc3RvLgoKQSBjb250aW51YWNpw7NuLCBvYnNlcnZhbW9zIHNpIGxhcyB2YXJpYWJsZXMgbnVtw6lyaWNhcyAtIHkgZnVuZGFtZW50YWxtZW50ZSBlbCBwZXNvIC0gZXN0w6FuIGNvcnJlbGFjaW9uYWRhcyBvIG5vLiBQYXJhIGVsbG8sIHNlIHByb2NlZGUgYSBhcGVydHVyYXIgcG9yIGVsIGfDqW5lcm8gZGUgY2FkYSB1bmEgZGUgbGFzIHBlcnNvbmFzOgoKYGBge3IgZWNobz1UUlVFLCBmaWcud2lkdGggPSAxMiwgZmlnLmhlaWdodCA9IDh9CmVuY3Vlc3RhX3NhbHVkICU+JQogIHNlbGVjdCh3aGVyZShpcy5udW1lcmljKSwgZ2VuZXJvLCAtcmVjb3JkKSAlPiUKICBnZ3BhaXJzKG1hcHBpbmcgPSBhZXMoY29sb3IgPSBnZW5lcm8pLCB0aXRsZSA9ICJNYXRyaXogZGUgY29ycmVsYWNpb25lcyIsCiAgICAgICAgICB1cHBlciA9IGxpc3QoY29udGludW91cyA9IHdyYXAoImNvciIsIHNpemUgPSA1LCBoanVzdD0wLjUpKSwgbGVnZW5kID0gMjUpICsKICB0aGVtZV9idygpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZT00NSwgdmp1c3Q9MC41KSwgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpCgpgYGAKCkVuIGxvcyBncsOhZmljb3MsIHNlIG9ic2VydmEgcXVlIGV4aXN0ZSBjaWVydGEgY29ycmVsYWNpw7NuIGVudHJlIGVudHJlIGVsIHBlc28geSBsYSBhbHR1cmEgKDAsNTcgcGFyYSBsYXMgcGVyc29uYXMgZGUgZ8OpbmVybyBtYXNjdWxpbm8sIDAsNDQgcGFyYSBsYXMgZGUgZ8OpbmVybyBmZW1lbmluby4gVGFtYmnDqW4gaGF5IHVuYSBjb3JyZWxhY2nDs24gbW9kZXJhZGEgZW50cmUgbGEgZWRhZCB5IGVsIHBlc28uCgpgYGB7ciBlY2hvPVRSVUV9CmVuY3Vlc3RhX3NhbHVkICU+JSAKICBncm91cF9ieShnZW5lcm8pICU+JSAKICBzdW1tYXJpc2UoY29yID0gY29yKGFsdHVyYSwgcGVzbykpCmBgYApcCkx1ZWdvLCBvYnNlcnZhbW9zIHF1w6kgY2F0ZWdvcsOtYXMgZXhpc3RlbiBwYXJhIGxhICJmcmVjdWVuY2lhIGRlIGhhbWJyZSBtZW5zdWFsIjoKCmBgYHtyIGVjaG89VFJVRX0KZW5jdWVzdGFfc2FsdWQgJT4lCiAgc2VsZWN0KGZyZWN1ZW5jaWFfaGFtYnJlX21lbnN1YWwpICU+JQogIHRhYmxlKCkgJT4lCiAgcHJvcC50YWJsZSgpICU+JQogIHNvcnQoZGVjcmVhc2luZyA9IFRSVUUpCmBgYAoKXApQYXJhIGNhZGEgdW5hIGRlIGVsbGFzLCBvYnNlcnZhbW9zIGPDs21vIHNlIGRpc3RyaWJ1eWVuIGVsIGNvbnN1bW8gc2VtYW5hbCBkZSB2ZXJkdXJhIHkgZWwgY29uc3VtbyBzZW1hbmFsIGRlIGNvbWlkYSBncmFzYToKCmBgYHtyIGVjaG89VFJVRSwgaGVpZ2h0ID0gNiwgZmlnLmFsaWduPSdjZW50ZXInfQp0YWJsYSA9IGVuY3Vlc3RhX3NhbHVkICU+JQogIGZpbHRlcihmcmVjdWVuY2lhX2hhbWJyZV9tZW5zdWFsICE9ICdEYXRvIHBlcmRpZG8nKSAlPiUKICBmaWx0ZXIoY29uc3Vtb19zZW1hbmFsX3ZlcmR1cmEgIT0gJ0RhdG8gcGVyZGlkbycpICU+JQogIHNlbGVjdChjb25zdW1vX3NlbWFuYWxfdmVyZHVyYSwgZnJlY3VlbmNpYV9oYW1icmVfbWVuc3VhbCkgJT4lCiAgdGFibGUoKQp0YWJsYSA9IGFzLmRhdGEuZnJhbWUodGFibGEpCmNvbG5hbWVzKHRhYmxhKSA8LSBjKCJjb25zdW1vX3NlbWFuYWxfdmVyZHVyYSIsICJmcmVjdWVuY2lhX2hhbWJyZV9tZW5zdWFsIiwgInEiKQoKb3JkZXIgPSBjKCJOdW5jYSIsICJSYXJhIHZleiIsICJBbGd1bmFzIHZlY2VzIiwgIkNhc2kgc2llbXByZSIsICJTaWVtcHJlIikKdHlwZSA9IGMoIjQgbyBtw6FzIHZlY2VzIGFsIGTDrWEiLCAiMyB2ZWNlcyBhbCBkw61hIiwgIjIgdmVjZXMgYWwgZMOtYSIsICIxIHZleiBhbCBkw61hIiwgIjQgYSA2IHZlY2VzIGR1cmFudGUgbG9zIMO6bHRpbW9zIDcgZMOtYXMiLCAiMSBhIDMgdmVjZXMgZHVyYW50ZSBsb3Mgw7psdGltb3MgNyBkw61hcyIsICJObyBjb23DrSB2ZXJkdXJhcyBuaSBob3J0YWxpemFzIGR1cmFudGUgbG9zIMO6bHRpbW9zIDcgZMOtYXMiKQoKCmdncGxvdCh0YWJsYSkgKwogYWVzKHggPSBmcmVjdWVuY2lhX2hhbWJyZV9tZW5zdWFsLCAKICAgICBmaWxsID0gZmFjdG9yKHN0cl93cmFwKGNvbnN1bW9fc2VtYW5hbF92ZXJkdXJhLCAyMCksIGxldmVscyA9IHN0cl93cmFwKHR5cGUsIDIwKSksCiAgICAgd2VpZ2h0ID0gcSkgKwogZ2VvbV9iYXIocG9zaXRpb24gPSAiZmlsbCIpICsKIHNjYWxlX2ZpbGxfaHVlKGRpcmVjdGlvbiA9IDEpICsKIGNvb3JkX2ZsaXAoKSArCiB0aGVtZV9taW5pbWFsKCkgKwogdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpICsgCiB0aGVtZShsZWdlbmQudGl0bGU9ZWxlbWVudF9ibGFuaygpKSArIAogbGFicyh4ID0gIkZyZWN1ZW5jaWEgZGUgaGFtYnJlIiwgIHkgPSAiQ29uc3VtbyBkZSB2ZXJkdXJhIChwcm9wb3JjacOzbikiLCB0aXRsZSA9ICJDb25zdW1vIGRlIHZlcmR1cmFzIHkgY2FudGlkYWQgZGUgdmVjZXMgY29uIGhhbWJyZSBlbiBlbCBtZXMiKSArIAogc2NhbGVfeF9kaXNjcmV0ZShsaW1pdHMgPSBvcmRlcikKYGBgCgpFbiBlc3RvcyBncsOhZmljb3MsIHNlIHB1ZWRlIG9ic2VydmFyIHF1ZSBhcXVlbGxvcyBxdWUgc3VlbGVuIHBhc2FyIGhhbWJyZSAoc2llbXByZSBvIGNhc2kgc2llbXByZSkgcmVnaXN0cmFuIHVuIG1lbm9yIGNvbnN1bW8gZGUgdmVyZHVyYXMgeSBob3J0YWxpemFzLiBFbiBsb3MgcHJpbWVyb3MgdGFtYmnDqW4gaGF5IHVuIGFsdG8gcG9yY2VudGFqZSBkZSBwZXJlc29uYXMgcXVlIGNvbWVuIHZlcmR1cmFzIHkvbyBob3J0YWxpemFzIDQgbyBtw6FzIHZlY2VzIHBvciBkw61hIGxvIHF1ZSBkZSBhbGd1bmEgbWFuZXJhIHRhbWJpw6luIGhhYmxhIGRlIGxvIHBvY28gYmFsYW5jZWFkYXMgcXVlIHNvbiBzdXMgZGlldGFzLgoKXAoKIyMjIyBbKioyKSBNb2RlbG8gaW5pY2lhbCoqXXsudW5kZXJsaW5lfQoKTGEgcHJpbWVyYSBhbHRlcm5hdGl2YSBwYXJhIG1vZGVsYXIgZWwgcGVzbywgZXMgbGEgc2lndWllbnRlOgoKJCQKRShwZXNvKSA9IFxiZXRhX3swfSArIFxiZXRhX3sxfSBhbHR1cmErIFxiZXRhX3syfSBlZGFkKyBcYmV0YV97M30gZ2VuZXJvICsgXGJldGFfezR9IGRpYXNBY3RpdmlkYWRGIGlzaWNhU2VtYW5hbCArIFxiZXRhX3s1fSBjb25zdW1vRGlhcmlvQWxjb2hvbAokJAoKQWp1c3RvIGVsIG1vZGVsbyBsaW5lYWw6CgpgYGB7ciBlY2hvPVRSVUV9Cm1vZGVsb19zaW1wbGUgPSBsbShwZXNvIH4gYWx0dXJhICsgZWRhZCArIGdlbmVybyArIGRpYXNfYWN0aXZpZGFkX2Zpc2ljYV9zZW1hbmFsICsgY29uc3Vtb19kaWFyaW9fYWxjb2hvbCwgZGF0YT1lbmN1ZXN0YV9zYWx1ZCkKCnRpZHlfbW9kZWxvX3NpbXBsZSA8LSB0aWR5KG1vZGVsb19zaW1wbGUsIGNvbmYuaW50ID0gVFJVRSkKdGlkeV9tb2RlbG9fc2ltcGxlCmBgYApcCkVsIHZhbG9yIGRlICRcYmV0YV97MH0kIG5vcyBpbmRpY2EgcXVlIGVsIHBlc28gZXNwZXJhZG8gcGFyYSB1bmEgbXVqZXIgZGUgMCBjbSBkZSBhbHR1cmEsIDAgYcOxb3MsIHF1ZSBubyByZWFsaXphIGFjdGl2aWRhZCBmw61zaWNhIHkgcXVlIG5vIGNvbnN1bWUgYWxjb2hvbCBlcyBkZSAtNjguOTIga2cuCgpFbCBjb2VmaWNpZW50ZSBxdWUgbXVsdGlwbGljYSBhIGxhIGFsdHVyYSAkXGJldGFfezF9JCBlcyAwLjY1LiBTaWduaWZpY2EgcXVlIGFudGUgY2FkYSBjbSBhZGljaW9uYWwgZGUgYWx0dXJhIGVsIHBlc28gcHJvbWVkaW8gYXVtZW50YSBlbiAwLDY1IGtnIG1hbnRlbmllbmRvIHRvZGFzIGxhcyBvdHJhcyB2YXJpYWJsZXMgY29uc3RhbnRlcwoKRWwgY29lZmljaWVudGUgcXVlIG11bHRpcGxpY2EgYSBnZW5lcm9NYXNjdWxpbm8gZXMgMS4yNiwgZGUgbG8gcXVlIHNlIGRlZHVjZSBxdWUgZWwgcGVzbyBlc3BlcmFkbyBwYXJhIHVuYSBwZXJzb25hIHJlY2nDqW4gbmFjaWRhIGRlIGfDqW5lcm8gbWFzY3VsaW5vIGRlIDAgY20gZGUgYWx0dXJhLCBxdWUgbm8gcmVhbGl6YSBhY3RpdmlkYWQgZsOtc2ljYSBuaSBjb25zdW1lIGFsY29ob2wgZXMgZGUgLTY3LDY1ICgtNjguOTIgKyAxLjI2MjYpLgoKRWwgdmFsb3IgZGUgJFxiZXRhX3s0fSQgbm9zIG11ZXN0cmEgcXVlIGFudGUgY2FkYSBkw61hIGRlIGFjdGl2aWRhZCBmw61zaWNhIGFkaWNpb25hbCBlbCBwZXNvIHByb21lZGlvIGRpc21pbnV5ZSBlbiAwLDA4a2cgKG1hbnRlbmllbmRvIHRvZGFzIGxhcyBvdHJhcyB2YXJpYWJsZXMgY29uc3RhbnRlcykuCgpFbCB2YWxvciBkZSAkXGJldGFfezV9JCBub3MgaW5kaWNhIHF1ZSBhbnRlIGNhZGEgdHJhZ28gZGlhcmlvIGFkaWNpb25hbCBkZSBhbGNvaG9sIGVsIHBlc28gZXNwZXJhZG8gZGUgdW5hIHBlcnNvbmEgYXVtZW50YSBlbiAwLDAwN2tnIChtYW50ZW5pZW5kbyB0b2RhcyBsYXMgb3RyYXMgdmFyaWFibGVzIGNvbnN0YW50ZXMpLgoKU2luIGVtYmFyZ28sIG5vIHRvZGFzIGxhcyB2YXJpYWJsZXMgc29uIHNpZ25pZmljYXRpdmFzLiBUcmFzIHJlYWxpemFyIGVsIHRlc3QgZGUgc2lnbmlmaWNhdGl2aWRhZCBpbmRpdmlkdWFsIHBhcmEgbG9zIGTDrWFzIGRlIGFjdGl2aWRhZCBmw61zaWNhIHNlbWFuYWwgeSBwYXJhIGVsIGNvbnN1bW8gZGlhcmlvIGRlIGFsY29ob2wgc2Ugb2JzZXJ2YW4gcC12YWxvcmVzIG1heW9yZXMgYSAwLjA1IHBvciBsbyBxdWUgbm8gc2UgcmVjaGF6YSBsYSBoaXDDs3Rlc2lzIG51bGEgKCRcYmV0YV97a309MCQpLiBUYW1iacOpbiBzZSBwdWVkZSB2ZXIgcXVlIGxvcyBpbnRlcnZhbG9zIGRlIGNvbmZpYW56YSBwYXJhIGFtYm9zIGNvZWZpY2llbnRlcyBjb250aWVuZW4gZWwgMCwgbG8gcXVlIG5vcyBkYSBsYSBwYXV0YSBkZSBxdWUgbm8gc29uIHZhcmlhYmxlcyDDunRpbGVzIHBhcmEgZXhwbGljYXIgZWwgcGVzbyBkZSB1bmEgcGVyc29uYS4KClBvciBvdHJvIGxhZG8sIGVsIHJlc3RvIGRlIGxhcyB2YXJpYWJsZXMgKGFsdHVyYSwgZWRhZCB5IGfDqW5lcm8pIHPDrSByZXN1bHRhbiBlc3RhZMOtc3RpY2FtZW50ZSBzaWduaWZpY2F0aXZhcyBwYXJhIGV4cGxpY2FyIGVsIHBlc28gZGUgdW5hIHBlcnNvbmEuCgpgYGB7ciBlY2hvPVRSVUV9CmdsYW5jZShtb2RlbG9fc2ltcGxlKQpgYGAKCkVsICRSXnsyfSQgZGVsIG1vZGVsbywgZWwgcG9yY2VudGFqZSBkZSB2YXJpYWJpbGlkYWQgZGVsIGZlbsOzbWVubyBxdWUgZWwgbW9kZWxvIGxvZ3JhIGV4cGxpY2FyLCBlcyBkZSAwLDM1LiBDb25zaWRlcmFuZG8gZWwgcC12YWxvciwgc2UgcmVjaGF6YSBsYSBoaXDDs3Rlc2lzIG51bGEgZGVsIHRlc3QgZGUgc2lnbmlmaWNhdGl2aWRhZCBnbG9iYWwgeSBwb2RlbW9zIGNvbmNsdWlyIHF1ZSBhbCBtZW5vcyB1bmEgZGUgbGFzIHZhcmlhYmxlcyByZWdyZXNvcmFzIHNpcnZlIHBhcmEgZXhwbGljYXIgZWwgcGVzbyBkZSB1bmEgcGVyc29uYS4KClwKCiMjIyMgWyoqMykgTW9kZWxvIGNhdGVnw7NyaWNhcyoqXXsudW5kZXJsaW5lfQoKTHVlZ28sIHNlIHByb2JhcsOhIGNvbiB1biBtb2RlbG8gcXVlIGluY29wb3JhIGVsIGNvbnN1bW8gc2VtYW5hbCBkZSBzbmFja3MgeSB1bmEgaW50ZXJhY2Npw7NuIGVudHJlIGVsIGfDqW5lcm8geSBsYSBlZGFkLCBlbiBsdWdhciBkZSBhY3RpdmlkYWQgZsOtc2ljYSB5IGNvbnN1bW8gZGUgYWxjb2hvbDoKCiQkCkUocGVzbykgPSBcYmV0YV97MH0gKyBcYmV0YV97MX0gYWx0dXJhKyBcYmV0YV97Mn0gZWRhZCsgXGJldGFfezN9IGdlbmVybyArIFxiZXRhX3s0fSBjb25zdW1vU2VtYW5hbFNuYWNrcyArIFxiZXRhX3s1fSBnZW5lcm8uZWRhZAokJAoKU2UgaGFyw6FuIGFsZ3VuYXMgbW9kaWZpY2FjaW9uZXMgcGFyYSBxdWUgZWwgbml2ZWwgYmFzYWwgZGVsIGNvbnN1bW8gc2VtYW5hbCBkZSBzbmFja3Mgc2VhICJubyBjb23DrSBjb21pZGEgc2FsYWRhIG8gc25hY2tzIGVuIGxvcyDDumx0aW1vcyA3IGTDrWFzOgoKYGBge3IgZWNobz1UUlVFfQplbmN1ZXN0YV9zYWx1ZCRjb25zdW1vX3NlbWFuYWxfc25hY2tzIDwtIHJlbGV2ZWwoYXMuZmFjdG9yKGVuY3Vlc3RhX3NhbHVkJGNvbnN1bW9fc2VtYW5hbF9zbmFja3MpLCByZWYgPSAiTm8gY29tw60gY29taWRhIHNhbGFkYSBvIHNuYWNrcyBlbiBsb3Mgw7psdGltb3MgNyBkw61hcyIpCgptb2RlbG9fY2F0ZWdvcmljYXMgPSBsbShwZXNvIH4gYWx0dXJhICsgZWRhZCArIGdlbmVybyArIGNvbnN1bW9fc2VtYW5hbF9zbmFja3MgKyBnZW5lcm8gKiBlZGFkLCBkYXRhPWVuY3Vlc3RhX3NhbHVkKQoKdGlkeV9tb2RlbG9fY2F0ZWdvcmljYXMgPC0gdGlkeShtb2RlbG9fY2F0ZWdvcmljYXMsIGNvbmYuaW50ID0gVFJVRSkKdGlkeV9tb2RlbG9fY2F0ZWdvcmljYXMKYGBgClwKRW4gZXN0ZSBjYXNvICRcYmV0YV97MH0kIG5vcyBpbmRpY2EgcXVlIGVsIHBlc28gcHJvbWVkaW8gZGUgdW5hIG11amVyIGRlIDAgYcOxb3MsIDAgY20sIHF1ZSBubyBjb25zdW1lIGNvbWlkYSBzYWxhZGEgbmkgc25hY2tzIGVzIGRlIC02NC4yIGtnLgoKTGFzIGRpc3RpbnRhcyBjYXRlZ29yw61hcyBkZSBjb25zdW1vU2VtYW5hbFNuYWNrcyAocG9yIGVqZW1wbG8sIGNvbnN1bW9fc2VtYW5hbF9zbmFja3MxIGEgMyB2ZWNlcyBkdXJhbnRlIGxvcyDDumx0aW1vcyA3IGTDrWFzKSByZWZsZWphbiBsYSBkaWZlcmVuY2lhIGRlIHBlc28gZW50cmUgdW5hIHBlcnNvbmEgcXVlIG5vIGNvbnN1bWUgc25hY2tzIHZzIHVuYSBwZXJzb25hIHF1ZSBjb25zdW1lIHNuYWNrcyBjb24gbGEgZnJlY3VlbmNpYSBxdWUgcmVmbGVqYSBsYSBjYXRlZ29yw61hLiBObyB0b2RhcyBsYXMgY2F0ZWdvcsOtYXMgZGUgY29uc3VtbyBzZW1hbmFsIGRlIHNuYWNrcyByZXN1bHRhbiBzaWduaWZpY2F0aXZhczogYXF1ZWxsYXMgcXVlIHN1cmdlbiBkZSBjb25zdW1pciBzbmFja3MgdG9kb3MgbG9zIGTDrWFzIHNvbiBsYXMgcXVlIG5vIGxvIHNvbi4KClBvciBvdHJvIGxhZG8sIGVsIGNvZWZpY2llbnRlIGVkYWQuZ2VuZXJvTWFzY3VsaW5vIGVuIGVzdGUgY2FzbyByZWZsZWphIHF1ZSwgcGFyYSBsb3MgaG9tYnJlcywgdW4gYcOxbyBhZGljaW9uYWwgZGUgZWRhZCBhdW1lbnRhIGVsIHBlc28gcHJvbWVkaW8gZW4gMSwyMiArIDAsMzkga2cgbWFudGVuaWVuZG8gY29uc3RhbnRlcyBlbCByZXN0byBkZSBsYXMgdmFyaWFibGVzLiBMYSB2YXJpYWJsZSB0aWVuZSB1biBlZmVjdG8gc2lnbmlmaWNhdGl2byBwYXJhIGV4cGxpY2FyIGVsIHBlc28uCgpWYW1vcyBhIHZlciBxdcOpIHBvcmNlbnRhamUgZGUgbGEgdmFyaWFiaWxpZGFkIGV4cGxpY2EgZWwgbW9kZWxvOgoKYGBge3IgZWNobz1UUlVFfQpnbGFuY2UobW9kZWxvX2NhdGVnb3JpY2FzKQpgYGAKXApQb3Igw7psdGltbywgY29uc2lkZXJhbmRvIHF1ZSBhbGd1bmFzIGNhdGVnb3LDrWFzIGNvbnN1bW9TZW1hbmFsU25hY2tzIG5vIHJlc3VsdGFuIHNpZ25pZmljYXRpdmFzLCBzZSB2YSBhIHJlYWxpemFyIHVuIHRlc3QgRiBwYXJhIGV2YWx1YXIgbGEgc2lnbmlmaWNhdGl2aWRhZCBjb25qdW50YSBkZSBsYSB2YXJpYWJsZSBwYXJhIGV4cGxpY2FyIGFsIHBlc28uCgpgYGB7cn0KdGlkeShhbm92YShtb2RlbG9fY2F0ZWdvcmljYXMpKQpgYGAKCkNvbnNpZGVyYW5kbyBlbCBwLXZhbG9yLCBzZSBjb25jbHV5ZSBxdWUgbGEgdmFyaWFibGUgY29uc3Vtb19zZW1hbmFsX3NuYWNrcyBlcyBzaWduaWZpY2F0aXZhIHBhcmEgZXhwbGljYXIgZWwgcGVzbyBkZSB1bmEgcGVyc29uYS4KCkEgY29udGludWFjacOzbiwgc2UgdmEgYSBwcm9wb25lciB1biBudWV2YSBkZWZpbmljacOzbiBkZSBsYXMgY2F0ZWdvcsOtYXMgZGUgY29uc3Vtb1NlbWFuYWxTbmFja3M6IHNlIHZhbiBhIGp1bnRhciAiY29uc3VtZSBzbmFja3MgMSB2ZXogYWwgZMOtYSIsICJjb25zdW1lIHNuYWNrcyAyIHZlY2VzIGFsIGTDrWEiLCAiY29uc3VtZSBzbmFja3MgMyB2ZWNlcyBhbCBkw61hIiB5ICJjb25zdW1lIHNuYWNrcyA0IG8gbcOhcyB2ZWNlcyBhbCBkw61hIiBlbiB1bmEgw7puaWNhIGNhdGVnb3LDrWEgImNvbnN1bWUgc25hY2tzIHRvZG9zIGxvcyBkw61hcyIsIHkgc2UgdmFuIGEgdm9sdmVyIGEgZXN0aW1hciBsb3MgY29lZmljaWVudGVzLgoKYGBge3J9CmVuY3Vlc3RhX3NhbHVkJGNvbnN1bW9fc2VtYW5hbF9zbmFja3NfbmV3IDwtIGlmZWxzZShlbmN1ZXN0YV9zYWx1ZCRjb25zdW1vX3NlbWFuYWxfc25hY2tzICVpbiUgYygiMSB2ZXogYWwgZMOtYSIsICIyIHZlY2VzIGFsIGTDrWEiLCAiMyB2ZWNlcyBhbCBkw61hIiwgIjQgbyBtw6FzIHZlY2VzIGFsIGTDrWEiKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQ29uc3VtZSBzbmFja3MgdG9kb3MgbG9zIGTDrWFzIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcy5jaGFyYWN0ZXIoZW5jdWVzdGFfc2FsdWQkY29uc3Vtb19zZW1hbmFsX3NuYWNrcykpCmBgYAoKYGBge3J9CmVuY3Vlc3RhX3NhbHVkJGNvbnN1bW9fc2VtYW5hbF9zbmFja3NfbmV3IDwtIHJlbGV2ZWwoYXMuZmFjdG9yKGVuY3Vlc3RhX3NhbHVkJGNvbnN1bW9fc2VtYW5hbF9zbmFja3NfbmV3KSwgcmVmID0gIk5vIGNvbcOtIGNvbWlkYSBzYWxhZGEgbyBzbmFja3MgZW4gbG9zIMO6bHRpbW9zIDcgZMOtYXMiKQoKbW9kZWxvX2NhdGVnb3JpY2FzX2dyb3VwZWQgPSBsbShwZXNvIH4gYWx0dXJhICsgZWRhZCArIGdlbmVybyArIGNvbnN1bW9fc2VtYW5hbF9zbmFja3NfbmV3ICsgZ2VuZXJvICogZWRhZCwgZGF0YT1lbmN1ZXN0YV9zYWx1ZCkKCnRpZHlfbW9kZWxvX2NhdGVnb3JpY2FzX2dyb3VwZWQgPC0gdGlkeShtb2RlbG9fY2F0ZWdvcmljYXNfZ3JvdXBlZCwgY29uZi5pbnQgPSBUUlVFKQp0aWR5X21vZGVsb19jYXRlZ29yaWNhc19ncm91cGVkCgpgYGAKClwKU2Ugb2JzZXJ2YSBxdWUgdG9kYXMgbGFzIHZhcmlhYmxlcyBpbmNsdcOtZGFzIGVuIGVsIG51ZXZvIG1vZGVsbyBzb24gc2lnbmlmaWNhdGl2YXMgKGluY2x1w61kYXMgdG9kYXMgbGFzIGNhdGVnb3LDrWFzIGRlIGNvbnN1bW8gc2VtYW5hbCBkZSBzbmFja3MpLgoKQSBjb250aW51YWNpw7NuLCBzZSB2YSBhIGNvbnN1bHRhciBsYSB2YXJpYWJpbGlkYWQgZXhwbGljYWRhIHBvciBlbCBtb2RlbG86CgpgYGB7cn0KZ2xhbmNlKG1vZGVsb19jYXRlZ29yaWNhc19ncm91cGVkKQpgYGAKQ29uIGxhIG51ZXZhIGFncnVwYWNpw7NuIG5vIGhheSB1bmEgbWVqb3JhIGVuIGVsICRSXjIkIGRlbCBtb2RlbG8uCgpcCgojIyMjIFsqKjQpIE1vZGVsb3MgcHJvcGlvcyB5IGV2YWx1YWNpw7NuKipdey51bmRlcmxpbmV9CgpBIGNvbnRpbnVhY2nDs24sIHNlIHN1Z2llcmVuIGRvcyBwb3NpYmxlcyBtb2RlbG9zIGFkaWNpb25hbGVzIHBhcmEgaW50ZW50YXIgZXhwbGljYXIgZWwgcGVzbyBkZSB1bmEgcGVyc29uYToKCkE6IAokJApFKHBlc28pID0gXGJldGFfezB9ICsgXGJldGFfezF9IGFsdHVyYSsgXGJldGFfezJ9IGVkYWQrIFxiZXRhX3szfSBnZW5lcm8gKyBcYmV0YV97NH0gY29uc3Vtb1NlbWFuYWxTbmFja3NOZXcgKyBcXCBcYmV0YV97NX0gY29uc3Vtb1NlbWFuYWxHYXNlb3NhICsgXGJldGFfezZ9IGRpYXNDb25zdW1vQ29taWRhUmFwaWRhICsgXGJldGFfezd9IGdlbmVyby5lZGFkICsgXGJldGFfezh9IGdlbmVyby5hbHR1cmEKJCQgCgpCOgoKJCQKRShwZXNvKSA9IFxiZXRhX3swfSArIFxiZXRhX3sxfSBhbHR1cmErIFxiZXRhX3syfSBlZGFkKyBcYmV0YV97M30gZ2VuZXJvICsgXGJldGFfezR9IGNvbnN1bW9TZW1hbmFsU25hY2tzTmV3ICsgXGJldGFfezV9IGZyZWN1ZW5jaWFIYW1icmVNZW5zdWFsICsgXFwgXGJldGFfezZ9IGdlbmVyby5lZGFkICsgXGJldGFfezd9IGdlbmVyby5hbHR1cmEgKyBcYmV0YV97OH0gZWRhZENvbnN1bW9BbGNvaG9sIC4gY29uc3Vtb0RpYXJpb0FsY29ob2wKJCQKCkVsIHByaW1lciBtb2RlbG8gcHJvcHVlc3RvIGluY29ycG9yYSB1bmEgaW50ZXJhY2Npw7NuIGVudHJlIGfDqW5lcm8geSBhbHR1cmEsIHkgZWwgY29uc3VtbyBzZW1hbmFsIGRlIGdhc2Vvc2EgeSBkZSBjb21pZGEgcsOhcGlkYS4KCkVsIHNlZ3VuZG8gbW9kZWxvIHByb3B1ZXN0bywgYWRlbcOhcyBkZSBsYSBpbnRlcmFjY2nDs24gZW50cmUgZ8OpbmVybyB5IGFsdHVyYSwgaW5jb3Jwb3JhIGxhIGZyZWN1ZW5jaWEgZGUgaGFtYnJlIG1lbnN1YWwuIHkgdGFtYmnDqW4gdW5hIGludGVyYWNjacOzbiBxdWUgY29uc2lzdGUgZW4gbXVsdGlwbGljYXIgbGEgZWRhZCBlbiBsYSBxdWUgY29tZW56YXJvbiBhIGNvbnN1bWlyIGFsY29ob2wgeSBlbCBjb25zdW1vIGRpYXJpby4KCmBgYHtyfQptb2RlbG9fYSA9IGxtKHBlc28gfiBhbHR1cmEgKyBlZGFkICsgZ2VuZXJvICsgY29uc3Vtb19zZW1hbmFsX3NuYWNrc19uZXcgKyBjb25zdW1vX3NlbWFuYWxfZ2FzZW9zYXMgKyBkaWFzX2NvbnN1bW9fY29taWRhX3JhcGlkYSArIGdlbmVybyAqIGVkYWQgKyBnZW5lcm8gKiBhbHR1cmEsIGRhdGE9ZW5jdWVzdGFfc2FsdWQpCgp0aWR5X21vZGVsb19hIDwtIHRpZHkobW9kZWxvX2EsIGNvbmYuaW50ID0gVFJVRSkKdGlkeV9tb2RlbG9fYQpgYGAKCmJyZXZlIGNvbWVudGFyaW8gZGUgbGEgc2lnbmlmaWNhdGl2aWRhZCBkZSBsYXMgdmFyaWFibGVzLgoKYGBge3J9Cm1vZGVsb19iID0gbG0ocGVzbyB+IGFsdHVyYSArIGVkYWQgKyBnZW5lcm8gKyBjb25zdW1vX3NlbWFuYWxfc25hY2tzX25ldyArIGZyZWN1ZW5jaWFfaGFtYnJlX21lbnN1YWwgKyBnZW5lcm8gKiBlZGFkICsgZ2VuZXJvICogYWx0dXJhICsgZWRhZF9jb25zdW1vX2FsY29ob2wgKiBjb25zdW1vX2RpYXJpb19hbGNvaG9sLCBkYXRhPWVuY3Vlc3RhX3NhbHVkKQoKdGlkeV9tb2RlbG9fYiA8LSB0aWR5KG1vZGVsb19iLCBjb25mLmludCA9IFRSVUUpCnRpZHlfbW9kZWxvX2IKYGBgCgpicmV2ZSBjb21lbnRhcmlvIGRlIGxhIHNpZ25pZmljYXRpdmlkYWQgZGUgbGFzIHZhcmlhYmxlcy4KCkEgY29udGludWFjacOzbiwgc2UgdmFuIGEgY29tcGFyYXIgbG9zIGRpc3RpbnRvcyBtb2RlbG9zIGRlc2Fycm9sbGFkb3MuIEVuIHByaW1lciBsdWdhciwgc2UgY2FyZ2EgZWwgZGF0YXNldCBkZSB0ZXN0IGVuIGVsIHF1ZSB2YW1vcyBhIG1lZGlyIGxhIHBlcmZvcm1hbmNlIGRlIGxvcyBtb2RlbG9zOgoKYGBge3J9CmVuY3Vlc3RhX3NhbHVkX3Rlc3QgPC0gcmVhZC5jc3YoImVuY3Vlc3RhX3NhbHVkX3Rlc3QuY3N2IikKYGBgCgpTZSBjcmVhIGxhIHZhcmlhYmxlIGNvbnN1bW8gc2VtYW5hbCBzbmFja3MgY29uIGxhIG51ZXZhIGFncnVwYWNpw7NuIHRhbWJpw6luIHBhcmEgZWwgZGF0YXNldCBkZSB0ZXN0OgoKYGBge3J9CmVuY3Vlc3RhX3NhbHVkX3Rlc3QkY29uc3Vtb19zZW1hbmFsX3NuYWNrc19uZXcgPC0gaWZlbHNlKGVuY3Vlc3RhX3NhbHVkX3Rlc3QkY29uc3Vtb19zZW1hbmFsX3NuYWNrcyAlaW4lIGMoIjEgdmV6IGFsIGTDrWEiLCAiMiB2ZWNlcyBhbCBkw61hIiwgIjMgdmVjZXMgYWwgZMOtYSIsICI0IG8gbcOhcyB2ZWNlcyBhbCBkw61hIiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkNvbnN1bWUgc25hY2tzIHRvZG9zIGxvcyBkw61hcyIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXMuY2hhcmFjdGVyKGVuY3Vlc3RhX3NhbHVkX3Rlc3QkY29uc3Vtb19zZW1hbmFsX3NuYWNrcykpCmBgYAoKU2UgYXJtYSB1bmEgbGlzdGEgY29uIHRvZG9zIGxvcyBtb2RlbG9zIGNvbnN0cnXDrWRvcwoKYGBge3J9Cm1vZGVscyA8LSBsaXN0KG1vZGVsb19zaW1wbGUgPSBtb2RlbG9fc2ltcGxlLCAKICAgICAgICAgICAgICAgbW9kZWxvX2NhdGVnb3JpYXMgPSBtb2RlbG9fY2F0ZWdvcmljYXNfZ3JvdXBlZCwgCiAgICAgICAgICAgICAgIG1vZGVsb19hID0gbW9kZWxvX2EsIAogICAgICAgICAgICAgICBtb2RlbG9fYiA9IG1vZGVsb19iKQpgYGAKCkVuIGVsIGRhdGFzZXQgZGUgdHJhaW4sIHNlIG9ic2VydmEgZWwgcjIgZGUgdG9kb3MgbG9zIG1vZGVsb3MKCmBgYHtyfQpldmFsdWFjaW9uX3RyYWluID0gbWFwX2RmKG1vZGVscywgZ2xhbmNlLCAuaWQgPSAibW9kZWwiKSAlPiUKICAjIG9yZGVuYW1vcyBwb3IgUjIgYWp1c3RhZG8KICBhcnJhbmdlKGRlc2MoYWRqLnIuc3F1YXJlZCkpCgpldmFsdWFjaW9uX3RyYWluCmBgYAoKYnJldmUgY29tZW50YXJpbyBkZSBsb3MgcjIgeSBsb3MgcjIgYWp1c3QuCgpBIGNvbnRpbnVhY2nDs24sIHNlIHZhbiBhIGNhbGN1bGFyIFJNU0UgeSBNQUUgdGFudG8gcGFyYSB0cmFpbiBjb21vIHBhcmEgdGVzdDoKCmBgYHtyfQpsaXN0YV9wcmVkaWNjaW9uZXNfdHJhaW5pbmcgPSBtYXAoLnggPSBtb2RlbHMsIC5mID0gYXVnbWVudCkKbGlzdGFfcHJlZGljY2lvbmVzX3Rlc3RpbmcgPSBtYXAoLnggPSBtb2RlbHMsIC5mID0gYXVnbWVudCwgbmV3ZGF0YSA9IGVuY3Vlc3RhX3NhbHVkX3Rlc3QpCgpjYmluZChtYXBfZGZyKC54ID0gbGlzdGFfcHJlZGljY2lvbmVzX3RyYWluaW5nLCAuZiA9IHJtc2UsIHRydXRoID0gcGVzbywgZXN0aW1hdGUgPSAuZml0dGVkLCAuaWQ9Im1vZGVsbyIpICU+JSByZW5hbWUocm1zZV90cmFpbiA9IC5lc3RpbWF0ZSkgJT4lIHNlbGVjdChtb2RlbG8sIHJtc2VfdHJhaW4pICU+JSBhcnJhbmdlKG1vZGVsbyksCm1hcF9kZnIoLnggPSBsaXN0YV9wcmVkaWNjaW9uZXNfdHJhaW5pbmcsIC5mID0gbWFlLCB0cnV0aCA9IHBlc28sIGVzdGltYXRlID0gLmZpdHRlZCwgLmlkPSJtb2RlbG8iKSAlPiUgcmVuYW1lKG1hZV90cmFpbiA9IC5lc3RpbWF0ZSkgJT4lIGFycmFuZ2UobW9kZWxvKSAlPiUgc2VsZWN0KG1hZV90cmFpbiksCm1hcF9kZnIoLnggPSBsaXN0YV9wcmVkaWNjaW9uZXNfdGVzdGluZywgLmYgPSBybXNlLCB0cnV0aCA9IHBlc28sIGVzdGltYXRlID0gLmZpdHRlZCwgLmlkPSJtb2RlbG8iKSAlPiUgcmVuYW1lKHJtc2VfdGVzdCA9IC5lc3RpbWF0ZSkgJT4lIGFycmFuZ2UobW9kZWxvKSAlPiUgc2VsZWN0KHJtc2VfdGVzdCksCm1hcF9kZnIoLnggPSBsaXN0YV9wcmVkaWNjaW9uZXNfdGVzdGluZywgLmYgPSBtYWUsIHRydXRoID0gcGVzbywgZXN0aW1hdGUgPSAuZml0dGVkLCAuaWQ9Im1vZGVsbyIpICU+JSByZW5hbWUobWFlX3Rlc3QgPSAuZXN0aW1hdGUpICU+JSBhcnJhbmdlKG1vZGVsbykgJT4lIHNlbGVjdChtYWVfdGVzdCkpICU+JSBhcnJhbmdlKHJtc2VfdGVzdCkKCmBgYAoKUGFyYSBkZWZpbmlyIGN1YWwgZXMgZWwgbWVqb3IgbW9kZWxvLCBzZSBkZWNpZGUgY29tcGFyYXIgZWwgUk1TRSBlbiB0ZXN0IGRlIGxvcyBkaXN0aW50b3MgbW9kZWxvIGNvbiBlbCBvYmpldGl2byBkZSBlbnRlbmRlciBxdcOpIHRhbiBiaWVuIGdlbmVyYWxpemEgZWwgbW9kZWxvIGVuIGRhdG9zIG51ZXZvczoKClNlIG9ic2VydmEgcXVlIGVsIG1lam9yIG1vZGVsbyBlcyBYIHF1ZSB0aWVuZSBlbCBtZW5vciBSTVNFIGVuIHRlc3QuIHkgYWRlbcOhcywgc2Ugb2JzZXJ2YSBxdWUgY29pbmNpZGUgY29uIHF1ZSBlcyB0YW1iacOpbiBlbCBkZSBtZW5vciBYIFggWCBYCgpcCgojIyMjIFsqKjUpIERpYWduw7NzdGljbyBkZWwgbW9kZWxvKipdey51bmRlcmxpbmV9CgpFbiBsYSBzaWd1aWVudGUgc2VjY2nDs24sIHNlIG9ic2VydmFyw6EgZWwgY3VtcGxpbWllbnRvIGRlIGxvcyBzdXB1ZXN0b3MgZGVsIG1vZGVsbyBsaW5lYWwgcGFyYSBlbCBtb2RlbG8gaW5pY2lhbC4KCkxvcyBzdXB1ZXN0b3MgYSBhbmFsaXphciBzb24gbG9zIHNpZ3VpZW50ZXM6CgoKYGBge3IgZmlnLndpZHRoPSA2fQpwbG90KG1vZGVsb19zaW1wbGUpCmBgYAoKQWwgdXRpbGl6YXIgcGxvdCgpIHNvYnJlIGVsIG1vZGVsbyBhanVzdGFkbywgc2UgcHVlZGVuIG9ic2VydmFyIHZhcmlvcyBncsOhZmljb3MgcXVlIG5vcyB2YW4gYSBwZXJtaXRpciBhbmFsaXphciBsb3Mgc3VwdWVzdG9zIGRlbCBtb2RlbG8gbGluZWFsOgoKClJlc2lkdW9zIHZzIHZhbG9yZXMgcHJlZGljaG9zOiBObyBwYXJlY2UgZXhpc3RpciB1bmEgZXN0cnVjdHVyYSBjbGFyYSBlbnRyZSBsb3MgcmVzaWR1b3MgeSBsb3MgdmFsb3JlcyBwcmVkaWNob3MuIFN1Y2VkZSBhbGdvIHNpbWlsYXIgZW4gZWwgZ3LDoWZpY28gc2NhbGUtbG9jYXRpb24uCgpOb3JtYWwgUVEgcGxvdDogRWwgZXh0cmVtbyBzdXBlcmlvciBkZXJlY2hvIG5vIHNlIGFqdXN0YSBhIGxhIGRpc3RyaWJ1Y2nDs24gdGXDs3JpY2EsIGVuIGVzdGUgY2FzbyBsYSDiiLxOKDAsMSksIHBvciBsbyBxdWUgc2UgZGVkdWNlIHF1ZSBsb3MgcmVzaWR1b3MgZXN0YW5kYXJpemFkb3Mgbm8gc2lndWVuIGVzYSBkaXN0cmlidWNpw7NuLgoKUmVzaWR1YWwgdnMgbGV2ZXJhZ2U6IEV4aXN0ZW4gYWxndW5vcyBwdW50b3MgY29uIHVuIGxldmVyYWdlIGJhc3RhbnRlIGFsdG8uIFZhbW9zIGEgdmVyIGN1w6FsIGVzIGxhIG9ic2VydmFjacOzbiBwYXJhIGVudGVuZGVyIHNpIHNlIHRyYXRhIGRlIHVuIHBvc2libGUgb3V0bGllcjoKCgpgYGB7cn0KbGlzdGFfcHJlZGljY2lvbmVzX3RyYWluaW5nJG1vZGVsb19zaW1wbGUgJT4lCiAgZmlsdGVyKC5oYXQgPT0gbWF4KC5oYXQpKQoKYGBgCgojIyMjIFsqKjYpIE1vZGVsbyByb2J1c3RvKipdey51bmRlcmxpbmV9CgpQb3Igw7psdGltbywgc2UgdmEgYSBsZWVyIHVuIG51ZXZvIGRhdGFzZXQgY29uIGFsZ3Vub3MgdmFsb3JlcyBhdMOtcGljb3MgeSB2YW1vcyBhIHZvbHZlciBhIG9ic2VydmFyIGxhIHJlbGFjacOzbiBlbnRyZSBwZXNvIHkgYWx0dXJhOgoKYGBge3J9CmVuY3Vlc3RhX3NhbHVkX291dGxpZXJzIDwtIHJlYWQuY3N2KCJlbmN1ZXN0YV9zYWx1ZF9tb2RlbG82LmNzdiIpCmBgYAoKT2JzZXJ2YW1vcyBlbCBtb2RlbG8gYWp1c3RhZG86CgpgYGB7cn0KbW9kZWxvX3JvYnVzdG8gPC0gbG1Sb2IocGVzbyB+IGFsdHVyYSArIGVkYWQgKyBnZW5lcm8gKyBkaWFzX2FjdGl2aWRhZF9maXNpY2Ffc2VtYW5hbCArIGNvbnN1bW9fZGlhcmlvX2FsY29ob2wsZGF0YSA9IGVuY3Vlc3RhX3NhbHVkX291dGxpZXJzKQoKdGlkeV9tb2RlbG9fcm9idXN0byA8LSB0aWR5KG1vZGVsb19yb2J1c3RvKQp0aWR5X21vZGVsb19yb2J1c3RvCmBgYApjb2VmaWNpZW50ZXMgZXN0aW1hZG9zOgoKbG9zIGNvZWZpY2llbnRlcyBxdWUgbXVsdGlwbGljYW4gbGEgYWx0dXJhIHkgbGEgZWRhZCBubyBjYW1iaWFuIHN1c3RhbmNpYWxtZW50ZSwgeSBzaWd1ZW4gc2llbmRvIHNpZ25pZmljYXRpdm9zIHBhcmEgZXhwbGljYXIgZWwgcGVzbyBkZSB1bmEgcGVyc29uYS4gYWxnbyBzaW1pbGFyIHN1Y2VkZSBjb24gZWwgY29lZmljaWVudGUgcXVlIGFjb21wYcOxYSBhbCBnw6luZXJvLiBEw61hcyBkZSBhY3RpdmlkYWQgZsOtc2ljYSBzZW1hbmFsIHkgY29uc3VtbyBkaWFyaW8gZGUgYWxjb2hvbCBzaWd1ZW4gc2llbmRvIG5vIHNpZ25pZmljYXRpdmFzIHBhcmEgZXhwbGljYXIgZWwgcGVzby4gCgpBaG9yYSBvYnNlcnZhbW9zIGVsIHBvcmNlbnRhamUgZGUgbGEgdmFyaWFuemEgZXhwbGljYWRhIHBvciBlbCBtb2RlbG86CgpgYGB7cn0KZ2xhbmNlKG1vZGVsb19yb2J1c3RvKQpgYGAKc2Ugb2JzZXJ2YSB1bmEgYmFqYSBjb25zaWRlcmFibGUgZGVsICRSXjIkIGRlIGVzdGUgbnVldm8gbW9kZWxvLiBQYXNhIGRlIGNlcmNhIGRlbCAwLDM1IGEgdW4gMCwyOC4KCkEgY29udGludWFjacOzbiwgdmFtb3MgYSBvYnNlcnZhciBxdWUgdGFuIGJpZW4gcGVyZm9ybWEgZWwgbW9kZWxvIGVuIGRhdG9zIG51ZXZvcy4gU2UgdmEgYSB1dGlsaXphciBudWV2YW1lbnRlIGVsIFJNU0UgeSBlbCBNQUU6CgpgYGB7cn0KcGVzbyA9IHByZWRpY3QobW9kZWxvX3JvYnVzdG8sIG5ld2RhdGEgPSBlbmN1ZXN0YV9zYWx1ZF90ZXN0LCBzZS5maXQgPSBUUlVFKQoKZW5jdWVzdGFfc2FsdWRfdGVzdCQuZml0dGVkID0gcGVzbyRmaXQKCnJiaW5kKHJtc2UoZGF0YSA9IGVuY3Vlc3RhX3NhbHVkX3Rlc3QsIHRydXRoID0gcGVzbywgZXN0aW1hdGUgPSAuZml0dGVkKSwKbWFlKGRhdGEgPSBlbmN1ZXN0YV9zYWx1ZF90ZXN0LCB0cnV0aCA9IHBlc28sIGVzdGltYXRlID0gLmZpdHRlZCkpCgpgYGAKCnNpIGNvbXBhcmFtb3MgZWwgcm1zZSB5IGVsIG1hZSB2cyBsb3MgbW9kZWxvcyBhbnRlcmlvcm1lbnRlIGFuYWxpemFkb3MsIHNlIHB1ZWRlIHZlciBxdWUgbm8gc2UgZGV0ZXJpb3LDsyBtdWNobyBsYSBwcmVkaWNjacOzbiBkZWwgbW9kZWxvLiBzw60sIHBvZHLDrWEg